package com.zxl.security.core.social.weixin.connect;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@Slf4j
public class WeiXinOAuth2Template extends OAuth2Template {
    private String clientId;
    private String clientSecret;
    private String authorizeUrl;
    private String accessTokenUrl;
    private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";

    public WeiXinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        /** 默认发送clientId和ClientSecret是使用head去发送的，
         * 具体实现是使用拦截器PreemptiveBasicAuthClientHttpRequestInterceptor去发送，
         * 这里我们去设置*/
        setUseParametersForClientAuthentication(true);// 设置true，这样发请求的时候才能带上appId和appSecret
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.authorizeUrl = authorizeUrl;
        this.accessTokenUrl = accessTokenUrl;
    }

    @Override
    public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, MultiValueMap<String, String> additionalParameters) {
        // 这里我们重写微信的实现
        StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
        accessTokenRequestUrl.append("?appid=" + clientId);
        accessTokenRequestUrl.append("&secret=" + clientSecret);
        accessTokenRequestUrl.append("&code=" + authorizationCode);
        accessTokenRequestUrl.append("&grant_type=authorization_code");
//        accessTokenRequestUrl.append("&redirect_uri=" + redirectUri);
        return getAccessToken(accessTokenRequestUrl);
    }

    /**
     * 刷新access_token的处理
     * @param refreshToken
     * @param additionalParameters
     * @return
     */
    @Override
    public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
        StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
        refreshTokenUrl.append("?appid=" + clientId);
        refreshTokenUrl.append("&grant_type=refresh_token");
        refreshTokenUrl.append("&refresh_token=" + refreshToken);
        return getAccessToken(refreshTokenUrl);
    }

    /**
     * 处理微信获取access_token返回的相关信息
     *
     * @param accessTokenRequestUrl 获取access_token请求的url地址
     */
    private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
        log.info("微信获取access_token，请求的url = " + accessTokenRequestUrl);
        String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
        Map<String, Object> result = null;
        try {
            result = new ObjectMapper().readValue(response, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        String errcode = MapUtils.getString(result, "errcode");
        // 如果错误码是存在的，则证明获取access_token失败
        if (StringUtils.hasText(errcode)) {
            String errmsg = MapUtils.getString(result, "errmsg");
            throw new RuntimeException("获取access token失败, errcode:" + errcode + ", errmsg:" + errmsg);
        }
        WeiXinAccessGrant accessGrant = new WeiXinAccessGrant(
                MapUtils.getString(result, "access_token"),
                MapUtils.getString(result, "scope"),
                MapUtils.getString(result, "refresh_token"),
                MapUtils.getLong(result, "expires_in"));
        accessGrant.setOpenId(MapUtils.getString(result, "openid"));
        return accessGrant;
    }

    /**
     * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
     * @param parameters
     * @return
     */
    @Override
    public String buildAuthenticateUrl(OAuth2Parameters parameters) {
        return buildAuthUrl(authorizeUrl, parameters)
                .append("&appid=" + clientId)
                .append("&scope=snsapi_userinfo")
                .append("#wechat_redirect")
                .toString();
    }

    @Override
    public String buildAuthorizeUrl(OAuth2Parameters parameters) {
        return buildAuthenticateUrl(parameters);
    }

    private StringBuilder buildAuthUrl(String baseAuthUrl, OAuth2Parameters parameters) {
        StringBuilder authUrl = new StringBuilder(baseAuthUrl);
        authUrl.append("?response_type=code");
        for (Iterator<Map.Entry<String, List<String>>> additionalParams = parameters.entrySet().iterator(); additionalParams.hasNext(); ) {
            Map.Entry<String, List<String>> param = additionalParams.next();
            String name = formEncode(param.getKey());
            if (name.equals("redirect_uri")) {
                authUrl.append("&redirect_uri=" + formEncode("http://i2se3v.natappfree.cc/auth.html"));
                continue;
            }
            for (Iterator<String> values = param.getValue().iterator(); values.hasNext(); ) {
                authUrl.append('&').append(name);
                String value = values.next();
                if (StringUtils.hasLength(value)) {
                    authUrl.append('=').append(formEncode(value));
                }
            }
        }
        return authUrl;
    }

    private String formEncode(String data) {
        try {
            return URLEncoder.encode(data, "UTF-8");
        } catch (UnsupportedEncodingException ex) {
            // should not happen, UTF-8 is always supported
            throw new IllegalStateException(ex);
        }
    }

    @Override
    protected RestTemplate createRestTemplate() {
        RestTemplate restTemplate = super.createRestTemplate();
        // 添加String返回的处理转化器，因为微信返回的contentType是html/text类型的，默认的转化器没有处理字符串返回的
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }
}
